/**********************************************************************
*
* printf/sprintf V implementation
*
* Copyright (c) 2020 Dario Deledda. All rights reserved.
* Use of this source code is governed by an MIT license
* that can be found in the LICENSE file.
*
* This file contains the printf/sprintf functions
*
**********************************************************************/
module strconv
import strconv.ftoa
import strings

enum Char_parse_state {
	start
	norm_char
	field_char
	pad_ch
	len_set_start
	len_set_in

	check_type
	check_float
	check_float_in

	reset_params
}

enum Align_text {
	right = 0
	left
	center
}

/******************************************************************************
*
* Float conversion utility
*
******************************************************************************/
const(
	// rounding value
	dec_round = [
		f64(0.44),
		0.044,
		0.0044,
		0.00044,
		0.000044,
		0.0000044,
		0.00000044,
		0.000000044,
		0.0000000044,
		0.00000000044,
		0.000000000044,
		0.0000000000044,
		0.00000000000044,
		0.000000000000044,
		0.0000000000000044,
		0.00000000000000044,
		0.000000000000000044,
		0.0000000000000000044,
		0.00000000000000000044,
		0.000000000000000000044,
	]
)

// max float 1.797693134862315708145274237317043567981e+308

pub fn f64_to_str_lnd(f f64, dec_digit int) string {
	// we add the rounding value
	s := ftoa.f64_to_str(f + dec_round[dec_digit], 18)
	// check for +inf -inf Nan
	if s.len > 2 && (s[0] == `n` || s[1] == `i`) {
		return s
	}

	m_sgn_flag := false
	mut sgn        := 1
	mut b          := [26]byte 
	mut d_pos      := 1
	mut i          := 0
	mut i1         := 0
	mut exp        := 0
	mut exp_sgn    := 1

	mut dot_res_sp := -1

	// get sign and deciaml parts
	for c in s {
		if c == `-` {
			sgn = -1
			i++
		} else if c == `+` {
			sgn = 1
			i++
		}
		else if c >= `0` && c <= `9` {
			b[i1++] = c
			i++
		} else if c == `.` {
			if sgn > 0 {
				d_pos = i
			} else {
				d_pos = i-1
			}
			i++
		} else if c == `e` {
			i++
			break
		} else {
			return "[Float conversion error!!]"
		}
	}
	b[i1] = 0

	// get exponent
	if s[i] == `-` {
		exp_sgn = -1
		i++
	} else if s[i] == `+` {
		exp_sgn = 1
		i++
	}
	for c in s[i..] {
		exp = exp * 10 + int(c-`0`)
	}

	// allocate exp+32 chars for the return string
	//mut res := []byte{len:exp+32,init:`0`}
	mut res := [`0`].repeat(exp+32) // TODO: Slow!! is there other possibilities to allocate this?
	mut r_i := 0  // result string buffer index

	//println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}")

	if sgn == 1 {
		if m_sgn_flag {
			res[r_i++] = `+`
		}
	} else {
		res[r_i++] = `-`
	}

	i = 0
	if exp_sgn >= 0 {
		for b[i] != 0 {
			res[r_i++] = b[i]
			i++
			if i >= d_pos && exp >= 0 {
				if exp == 0 {
					dot_res_sp = r_i
					res[r_i++] = `.`
				}
				exp--
			}
		}
		for exp >= 0 {
			res[r_i++] = `0`
			exp--
		}
		//println("exp: $exp $r_i $dot_res_sp")
	} else {
		mut dot_p := true
		for exp > 0 {
			res[r_i++] = `0`
			exp--
			if dot_p  {
				dot_res_sp = r_i
				res[r_i++] = `.`
				dot_p = false
			}
		}
		for b[i] != 0 {
			res[r_i++] = b[i]
			i++
		}
	}
	//println("r_i-d_pos: ${r_i - d_pos}")
	if dot_res_sp >= 0 {
		if (r_i - dot_res_sp) > dec_digit {
			r_i = dot_res_sp + dec_digit + 1
		}
		res[r_i] = 0
		//println("result: [${tos(&res[0],r_i)}]")
		return tos(&res[0],r_i)
	} else {
		if dec_digit > 0 {
			mut c := 0
			res[r_i++] = `.`
			for c < dec_digit {
				res[r_i++] = `0`
				c++
			}
			res[r_i] = 0
		}
		return tos(&res[0],r_i)
	}
}

/******************************************************************************
*
* Single format functions
*
******************************************************************************/
struct BF_param {
	pad_ch       byte       = ` `     // padding char
	len0         int        = -1      // default len for whole the number or string
	len1         int        = 6       // number of decimal digits, if needed
	positive     bool       = true    // mandatory: the sign of the number passed
	sign_flag    bool       = false   // flag for print sign as prefix in padding
	allign       Align_text = .right  // alignment of the string
	rm_tail_zero bool       = false   // remove the tail zeros from floats
}

pub fn format_str(s string, p BF_param) string {
	dif := p.len0 - s.len
	if dif <= 0 {
		return s
	}
	mut res := strings.new_builder(s.len + dif)
	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	return res.str()
}

// max int64 9223372036854775807 
pub fn format_dec(d u64, p BF_param) string {
	mut s := ""
	mut res := strings.new_builder(20)
	mut sign_len_diff := 0
	if p.pad_ch == `0` {
		if p.positive {
			if p.sign_flag { 
				res.write_b(`+`)
				sign_len_diff = -1
			}
		} else {
			res.write_b(`-`)
			sign_len_diff = -1
		}
		s = d.str()
	} else {
		if p.positive {
			if p.sign_flag {
				s = "+" + d.str()
			} else {
				s = d.str()
			}
		} else {
			s = "-" + d.str()
		}
	}
	dif := p.len0 - s.len + sign_len_diff

	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	return res.str()
}

pub fn format_fl(f f64, p BF_param) string {
	mut s  := ""
	mut fs := f64_to_str_lnd(if f >= 0.0 {f} else {-f}, p.len1)
	
	// error!!
	if fs[0] == `[` {
		return fs
	}
	
	if p.rm_tail_zero {
		fs = remove_tail_zeros(fs)
	}
	mut res := strings.new_builder( if p.len0 > fs.len { p.len0 } else { fs.len })

	mut sign_len_diff := 0
	if p.pad_ch == `0` {
		if p.positive {
			if p.sign_flag { 
				res.write_b(`+`)
				sign_len_diff = -1
			}
		} else {
			res.write_b(`-`)
			sign_len_diff = -1
		}
		s = fs
	} else {
		if p.positive {
			if p.sign_flag {
				s = "+" + fs
			} else {
				s = fs
			}
		} else {
			s = "-" + fs
		}
	}

	dif := p.len0 - s.len + sign_len_diff

	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}

	return res.str()
}

pub fn format_es(f f64, p BF_param) string {
	mut s := ""
	mut fs := ftoa.f64_to_str_pad(if f> 0 {f} else {-f},p.len1)
	if p.rm_tail_zero {
		fs = remove_tail_zeros(fs)
	}
	mut res := strings.new_builder( if p.len0 > fs.len { p.len0 } else { fs.len })

	mut sign_len_diff := 0
	if p.pad_ch == `0` {
		if p.positive {
			if p.sign_flag { 
				res.write_b(`+`)
				sign_len_diff = -1
			}
		} else {
			res.write_b(`-`)
			sign_len_diff = -1
		}
		s = fs
	} else {
		if p.positive {
			if p.sign_flag {
				s = "+" + fs
			} else {
				s = fs
			}
		} else {
			s = "-" + fs
		}
	}

	dif := p.len0 - s.len + sign_len_diff
	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	return res.str()
}

pub fn remove_tail_zeros(s string) string {
	mut i := 0
	mut last_zero_start := -1
	mut dot_pos         := -1
	mut in_decimal := false
	mut prev_ch := byte(0)
	for i < s.len {
		ch := s.str[i]
		if ch == `.` {
			in_decimal = true
			dot_pos = i
		}
		else if in_decimal {
			if ch == `0` && prev_ch != `0` {
				last_zero_start = i
			} else if ch >= `1` && ch <= `9` {
				last_zero_start = -1
			} else if ch == `e` {
				break
			}
		}
		prev_ch = ch
		i++
	}

	mut tmp := ""
	if last_zero_start > 0 {
		if last_zero_start == dot_pos+1 {
			tmp = s[..dot_pos] + s [i..]
		}else {
			tmp = s[..last_zero_start] + s [i..]
		}
	} else {
		tmp = s
	}
	if tmp.str[tmp.len-1] == `.` {
		return tmp[..tmp.len-1]
	}
	return tmp
}

/******************************************************************************
*
* Main functions
*
******************************************************************************/
pub fn v_printf(str string, pt ... voidptr) {
	print(v_sprintf(str, pt))
}

pub fn v_sprintf(str string, pt ... voidptr) string{

	mut res := strings.new_builder(pt.len * 16)

	mut i            := 0                // main strign index
	mut p_index      := 0                // parameter index
	mut sign         := false            // sign flag
	mut allign       := Align_text.right
	mut len0         := -1               // forced length, if -1 free length
	mut len1         := -1               // decimal part for floats
	def_len1         := 6                // default value for len1
	mut pad_ch       := ` `              // pad char
	mut th_separator := false            // thousands separator flag

	// prefix chars for Length field 
	mut ch1 := `0`  // +1 char if present else `0`
	mut ch2 := `0`  // +2 char if present else `0`

	mut status := Char_parse_state.norm_char
	for i < str.len {
		if status == .reset_params {
			sign         = false
			allign       = .right
			len0         = -1
			len1         = -1
			pad_ch       = ` `
			th_separator = false
			status = .norm_char
			ch1 = `0`
			ch2 = `0`
			continue
		}

		ch := str[i]
		if ch != `%` && status == .norm_char {
			res.write_b(ch)
			i++
			continue
		}
		if ch == `%` && status == .norm_char {
			status = .field_char
			i++
			continue
		}

		// single char, manage it here
		if ch == `c` && status == .field_char {
			d1 := *(&byte(pt[p_index]))
			res.write_b(d1)
			status = .reset_params
			p_index++
			i++
			continue
		}

		// pointer, manage it here
		if ch == `p` && status == .field_char {
			res.write("0x"+ptr_str(pt[p_index]))
			status = .reset_params
			p_index++
			i++
			continue
		}

		if status == .field_char {
			mut fc_ch1 := `0`
			mut fc_ch2 := `0`
			if (i + 1) < str.len {
				fc_ch1 = str[i+1]
				if (i + 2) < str.len {
					fc_ch2 = str[i+2]
				}
			}
			if ch == `+` {
				sign = true
				i++
				continue
			} else if ch == `-` {
				allign = .left
				i++
				continue
			} else if ch in [`0`,` `] {
				if allign == .right {
					pad_ch = ch
				}
				i++
				continue
			} else if ch == `'` {
				th_separator = true
				i++
				continue
			} else if ch == `.` && fc_ch1 >= `1` && fc_ch1 <= `9` {
				status = .check_float
				i++
				continue
			} 
			// manage "%.*s" precision field
			else if ch == `.` && fc_ch1 == `*` && fc_ch2 == `s` {
				len := *(&int(pt[p_index]))
				p_index++
				mut s := *(&string(pt[p_index]))
				s = s[..len]
				p_index++
				res.write(s)
				status = .reset_params
				i += 3
				continue
			}
			status = .len_set_start
			continue
		}

		if status == .len_set_start {
			if ch >= `1` && ch <= `9` {
				len0 = int(ch - `0`)
				status = .len_set_in
				i++
				continue
			}
			if ch == `.` {
				status = .check_float
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .len_set_in {
			if ch >= `0` && ch <= `9` {
				len0 *= 10
				len0 += int(ch - `0`)
				i++
				continue
			}
			if ch == `.` {
				status = .check_float
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .check_float {
			if ch >= `0` && ch <= `9` {
				len1 = int(ch - `0`)
				status = .check_float_in
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .check_float_in {
			if ch >= `0` && ch <= `9` {
				len1 *= 10
				len1 += int(ch - `0`)
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .check_type {

			if ch == `l` {
				if ch1 == `0` {
					ch1 = `l`
					i++
					continue
				} else {
					ch2 = `l`
					i++
					continue
				}
			}
			else if ch == `h` {
				if ch1 == `0` {
					ch1 = `h`
					i++
					continue
				} else {
					ch2 = `h`
					i++
					continue
				}
			}

			// signed integer
			else if ch in [`d`,`i`] {
				mut d1 := u64(0)
				mut positive := true

				//println("$ch1 $ch2")
				match ch1 {
					// h for 16 bit int
					// hh fot 8 bit int
					`h` {
						if ch2 == `h` {
							x := *(&i8(pt[p_index]))
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u64(x) } else { u64(-x) }
						} else {
							x := *(&i16(pt[p_index]))
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u64(x) } else { u64(-x) }
						}
					}
					// l  i64
					// ll i64 for now
					`l` {
						// placeholder for future 128bit integer code
						/*
						if ch2 == `l` {
							x := *(&i128(pt[p_index]))
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u128(x) } else { u128(-x) }
						} else {
							x := *(&i64(pt[p_index]))
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u64(x) } else { u64(-x) }
						}
						*/
						x := *(&i64(pt[p_index]))
						positive = if x >= 0 { true } else { false }
						d1 = if positive { u64(x) } else { u64(-x) }
					}
					// defualt int
					else {
						x := *(&int(pt[p_index]))
						positive = if x >= 0 { true } else { false }
						d1 = if positive { u64(x) } else { u64(-x) }
					}

				}

				res.write(format_dec(d1,{pad_ch: pad_ch, len0: len0, len1: 0, positive: positive, sign_flag: sign, allign: allign}))
				status = .reset_params
				p_index++
				i++
				ch1 = `0`
				ch2 = `0`
				continue
			}

			// unsigned integer
			else if ch == `u` {
				mut d1 := u64(0)
				positive := true

				match ch1 {
					// h for 16 bit unsigned int
					// hh fot 8 bit unsigned int
					`h` {
						if ch2 == `h` {
							d1 = u64(*(&byte(pt[p_index])))
						} else {
							d1 = u64(*(&u16(pt[p_index])))
						}
					}
					// l  u64
					// ll u64 for now
					`l` {
						// placeholder for future 128bit integer code
						/*
						if ch2 == `l` {
							d1 = u128(*(&u128(pt[p_index])))
						} else {
							d1 = u64(*(&u64(pt[p_index])))
						}
						*/
						d1 = u64(*(&u64(pt[p_index])))
					}
					// defualt int
					else {
						d1 = u64(*(&u32(pt[p_index])))
					}
				}

				res.write(format_dec(d1,{pad_ch: pad_ch, len0: len0, len1: 0, positive: positive, sign_flag: sign, allign: allign}))
				status = .reset_params
				p_index++
				i++
				continue
			}

			// hex
			else if ch in [`x`, `X`] {
				mut s := ""

				match ch1 {
					// h for 16 bit int
					// hh fot 8 bit int
					`h` {
						if ch2 == `h` {
							x := *(&i8(pt[p_index]))
							s = x.hex()
						} else {
							x := *(&i16(pt[p_index]))
							s = x.hex()
						}
					}
					// l  i64
					// ll i64 for now
					`l` {
						// placeholder for future 128bit integer code
						/*
						if ch2 == `l` {
							x := *(&i128(pt[p_index]))
							s = x.hex()
						} else {
							x := *(&i64(pt[p_index]))
							s = x.hex()
						}
						*/
						x := *(&i64(pt[p_index]))
						s = x.hex()
					} 
					else {
						x := *(&int(pt[p_index]))
						s = x.hex()
					}
				}

				if ch == `X` {
					s = s.to_upper()
				}

				res.write(format_str(s,{pad_ch: pad_ch, len0: len0, len1: 0, positive: true, sign_flag: false, allign: allign}))
				status = .reset_params
				p_index++
				i++
				continue
			}

			// float and double
			if ch in [`f`, `F`] {
				x := *(&f64(pt[p_index]))
				mut positive := x >= f64(0.0)
				len1 = if len1 >= 0 { len1 } else { def_len1 }
				s := format_fl(f64(x), {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign})
				res.write(if ch == `F` {s.to_upper()} else {s})
				status = .reset_params
				p_index++
				i++
				continue
			}
			else if ch in [`e`, `E`] {
				x := *(&f64(pt[p_index]))
				mut positive := x >= f64(0.0)
				len1 = if len1 >= 0 { len1 } else { def_len1 }
				s := format_es(f64(x), {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign})
				res.write(if ch == `E` {s.to_upper()} else {s})
				status = .reset_params
				p_index++
				i++
				continue
			}
			else if ch in [`g`, `G`] {
				x := *(&f64(pt[p_index]))
				mut positive := x >= f64(0.0)
				mut s := ""
				tx := fabs(x)
				if tx < 999_999.0 && tx >= 0.00001 {
					//println("Here g format_fl [$tx]")
					len1 = if len1 >= 0 { len1+1 } else { def_len1 }
					s = format_fl(x, {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign, rm_tail_zero: true})
				} else {
					len1 = if len1 >= 0 { len1+1 } else { def_len1 }
					s = format_es(x, {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign, rm_tail_zero: true})
				}
				res.write(if ch == `G` {s.to_upper()} else {s})
				status = .reset_params
				p_index++
				i++
				continue
			}

			// string
			else if ch == `s` {
				s1 := *(&string(pt[p_index]))
				pad_ch = ` `
				res.write(format_str(s1, {pad_ch: pad_ch, len0: len0, len1: 0, positive: true, sign_flag: false, allign: allign}))
				status = .reset_params
				p_index++
				i++
				continue
			}
		}

		status = .reset_params
		p_index++
		i++
	}

	return res.str()
}

fn fabs(x f64) f64 {
	if x < 0.0 {
		return -x
	}
	return x
}
